#include <storages/postgres/detail/cancel.hpp>

#include <array>
#include <cstdint>

#include <userver/engine/io/sockaddr.hpp>
#include <userver/engine/io/socket.hpp>
#include <userver/storages/postgres/exceptions.hpp>

#ifdef __clang__
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define USERVER_IMPL_DISABLE_MSAN __attribute__((no_sanitize_memory))
#else
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define USERVER_IMPL_DISABLE_MSAN
#endif

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define PG_PROTOCOL_MAJOR(v) ((v) >> 16)
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define PG_PROTOCOL_MINOR(v) ((v) & 0x0000ffff)
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define PG_PROTOCOL(m, n) (((m) << 16) | (n))

// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define CANCEL_REQUEST_CODE PG_PROTOCOL(1234, 5678)

using ACCEPT_TYPE_ARG3 = socklen_t;

struct PgSockaddrStorage {
    union {
        struct sockaddr sa; /* get the system-dependent fields */
        int64_t ss_align;   /* ensures struct is properly aligned */
        char ss_pad[128];   /* ensures struct has desired size */
    } ss_stuff;
};

// NOLINTNEXTLINE(modernize-use-using)
typedef struct {
    struct PgSockaddrStorage addr;
    ACCEPT_TYPE_ARG3 salen;
} SockAddr;

struct pg_cancel {
    SockAddr raddr;
    int be_pid;
    int be_key;
};

// NOLINTNEXTLINE(modernize-use-using)
typedef struct CancelRequestPacket {
    /* Note that each field is stored in network byte order! */
    uint32_t cancel_request_code; /* code to identify a cancel request */
    uint32_t backend_pid;         /* PID of client's backend */
    uint32_t cancel_auth_code;    /* secret key to authorize cancel */
} CancelRequestPacket;

struct CancelPacket {
    uint32_t packetlen;
    CancelRequestPacket cp;
};

USERVER_NAMESPACE_BEGIN

namespace storages::postgres::detail {

namespace {

// TODO(TAXICOMMON-11213) investigate why memory sanitizer complains.
USERVER_IMPL_DISABLE_MSAN CancelPacket MakeCancelPacket(const PGcancel& cn) noexcept {
    CancelPacket cp{};
    cp.packetlen = sizeof(cp);
    cp.cp.cancel_request_code = static_cast<uint32_t>(htonl(CANCEL_REQUEST_CODE));
    cp.cp.backend_pid = htonl(cn.be_pid);
    cp.cp.cancel_auth_code = htonl(cn.be_key);
    return cp;
}

}  // namespace

USERVER_IMPL_DISABLE_MSAN void Cancel(PGcancel* cn, engine::Deadline deadline) {
    if (!cn) {
        return;
    }

    engine::io::Sockaddr addr;
    memcpy(addr.Data(), &cn->raddr, std::min<size_t>(sizeof(cn->raddr), addr.Size()));

    engine::io::Socket tmp_sock(addr.Domain(), engine::io::SocketType::kStream);

    tmp_sock.Connect(addr, deadline);

    CancelPacket cp = MakeCancelPacket(*cn);
    auto ret = tmp_sock.SendAll(&cp, sizeof(cp), deadline);
    if (ret != sizeof(cp)) {
        throw CommandError("SendAll()");
    }

    /*
     * Comment from libpq's sources, fe-connect.c, inside internal_cancel():
     *
     * Wait for the postmaster to close the connection, which indicates that
     * it's processed the request.  Without this delay, we might issue another
     * command only to find that our cancel zaps that command instead of the
     * one we thought we were canceling.  Note we don't actually expect this
     * read to obtain any data, we are just waiting for EOF to be signaled.
     */
    std::array<char, 1024> c{};
    [[maybe_unused]] auto ret2 = tmp_sock.RecvAll(c.data(), c.size(), deadline);
}

}  // namespace storages::postgres::detail

USERVER_NAMESPACE_END
